import { isDef, isUndef } from '../../util/index'
import { VNode } from './vnode'
import { registerRef } from './module/ref'

export const emptyNode = new VNode('', {}, [])

const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
function sameVnode(a, b) {
    return (
        isDef(a) && isDef(b) &&
        a.key === b.key && (
            a.tag === b.tag &&
            isDef(a.data) === isDef(b.data)
        )
    )
}

export function createPatchFunction(backend) {
    let i, j
    const cbs = {}
    const { nodeOps, modules } = backend

    for (i = 0; i < hooks.length; ++i) {
        cbs[hooks[i]] = []
        for (j = 0; j < modules.length; ++j) {
            if (isDef(modules[j][hooks[i]])) {
                cbs[hooks[i]].push(modules[j][hooks[i]])
            }
        }
    }
    function emptyNodeAt(elm) {
        return new VNode(elm.tagName.toLowerCase(), {}, [], undefined, elm)
    }
    function insert(parent, elm, ref) {
        if (isDef(parent)) {
            if (isDef(ref)) {
                if (ref.parentNode === parent) {
                    nodeOps.insertBefore(parent, elm, ref)
                }
            } else {
                nodeOps.appendChild(parent, elm)
            }
        }
    }
    function createElm(vnode, parentElm, refElm, nested, ownerArray, index) {
        //如果有注释节点，会解析到一些奇怪的内容这里的vnode为undefined，导致报错
        if (isUndef(vnode)) {
            return
        }
        vnode.isRootInsert = !nested
        if (createComponent(vnode, parentElm, refElm)) {
            return
        }
        const data = vnode.data
        const children = vnode.children
        const tag = vnode.tag

        if (isDef(tag)) {
            vnode.elm = document.createElement(tag)
            createChildren(vnode, children)
            if (isDef(data)) {
                invokeCreateHooks(vnode)
            }
            parentElm && insert(parentElm, vnode.elm, refElm)
        } else {
            vnode.elm = document.createTextNode(vnode.text)
            parentElm && insert(parentElm, vnode.elm, refElm)
        }
    }
    function createChildren(vnode, children) {
        if (Array.isArray(children)) {
            for (var i = 0; i < children.length; ++i) {
                createElm(children[i], vnode.elm, null, true, children, i);
            }
        }
    }
    function createComponent(vnode, parentElm, refElm) {
        let i = vnode.data
        if (isDef(i)) {
            //被keep-alive缓存的组件才会有keepAlive= true的属性
            const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
            if (isDef(i = i.hook) && isDef(i = i.init)) {
                //组件初始化
                i(vnode)
            }
            if (isDef(vnode.componentInstance)) {
                initComponent(vnode)
                insert(parentElm, vnode.elm, refElm)
                if (isReactivated === true) {
                    reactivateComponent(vnode, parentElm, refElm)
                }
                return true
            }
        }
    }
    function initComponent(vnode) {
        //设置真实元素
        vnode.elm = vnode.componentInstance.$el
        const data = vnode.data
        if (isDef(data) && isPatchable(vnode)) {
            invokeCreateHooks(vnode)
        }
        //注册ref
        registerRef(vnode)
    }
    function reactivateComponent(vnode ,parentElm ,refElm){

    }
    //判断组件是否已经解析成实例化
    function isPatchable(vnode) {
        while (vnode.componentInstance) {
            vnode = vnode.componentInstance._vnode
        }
        return isDef(vnode.tag)
    }
    function invokeCreateHooks(vnode) {
        //组件解析好了，但是还没有解析属性，cbs里面有很多解析属性的函数，这里逐一调用
        //create数组里面的函数有
        // updateAttrs, updateClass, updateDOMListeners, updateDOMProps, updateStyle,
        // _enter, _create, create, updateDirectives
        for (let i = 0; i < cbs.create.length; ++i) {
            cbs.create[i](emptyNode, vnode)
        }
        i = vnode.data.hook
        if (isDef(i)) {
            if (isDef(i.create)) i.create(emptyNode, vnode)
            //这里条件为真,调用当前组件的mounted方法
            if (isDef(i.insert)) i.insert(vnode)
        }
    }

    function patchVnode(
        oldVnode,
        vnode,
        ownerArray,
        index
    ) {
        // if( oldVnode.tag && oldVnode.tag.indexOf('vue-component') > -1){
        //     console.log(oldVnode);
        // }

        if (oldVnode === vnode) {
            return
        }
        const elm = vnode.elm = oldVnode.elm

        let i
        const data = vnode.data
   
        //更新props,插槽
        if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
            i(oldVnode, vnode)
        }

        const oldCh = oldVnode.children
        const ch = vnode.children

        // if (isDef(data)) {
        //     //更新vnode的属性,包括如下：
        //     for (i = 0; i < cbs.update.length; ++i) {
        //         cbs.update[i](oldVnode, vnode)
        //     }
        // }


        if (isUndef(vnode.text)) {
            if (isDef(oldCh) && isDef(ch)) {
                if (oldCh !== ch) updateChildren(elm, oldCh, ch)
            } else if (isDef(ch)) {
                for (let index = 0; index < ch.length - 1; index++) {
                    const newChild = ch[index];
                    createElm(newChild, elm, null, false, ch, index)
                }
            } else if (isDef(oldCh)) {
                for (let index = 0; index < oldCh.length - 1; index++) {
                    const child = oldCh[index] && oldCh[index].elm;
                    const parent = nodeOps.parentNode(child)
                    if (isDef(parent)) {
                        nodeOps.removeChild(parent, child)
                    }
                }
            } else if (isDef(oldVnode.text)) {
                elm.textContent = ''
            } 
        } else if (oldVnode.text !== vnode.text) {
            elm.textContent = vnode.text
        }
        // if (isDef(data)) {
        //     if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
        // }
    }


    function updateChildren(parentElm, oldCh, newCh) {
        let oldStartIdx = 0
        let newStartIdx = 0
        let oldEndIdx = newCh.length - 1
        let newEndIdx = newCh.length - 1

        let oldStartVNode = oldCh[0]
        let newStartVNode = newCh[0]
        let oldEndVNode = oldCh[oldEndIdx]
        let newEndVNode = newCh[newEndIdx]

        while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {

            if (!oldStartVNode) {  //undefined时前移一位
                oldStartVNode = oldCh[++oldStartIdx]
            } else if (!oldEndVNode) {  //undefined时后移一位
                oldEndVNode = oldCh[--oldEndIdx] 
            } else if (sameVnode(oldStartVNode, newStartVNode)) {  //1.开始与开始
                patchVnode(oldStartVNode, newStartVNode, newCh, newStartIdx)
                oldStartVNode = oldCh[++oldStartIdx]
                newStartVNode = newCh[++newStartIdx]
            } else if (sameVnode(oldEndVNode, newEndVNode)) {      //2.结束与结束    
                patchVnode(oldEndVNode, newEndVNode, newCh, newEndIdx)
                oldEndVNode = oldCh[--oldEndIdx]
                newEndVNode = newCh[--newEndIdx]
            } else if (sameVnode(oldStartVNode, newEndVNode)) { //3.开始与结束
                patchVnode(oldStartVNode, newEndVNode, newCh, newEndIdx)
                nodeOps.insertBefore(parentElm, oldStartVNode.elm, nodeOps.nextSibling(oldEndVNode.elm))
                // 更新索引，指向下一个位置
                oldStartVNode = oldCh[++oldStartIdx]
                newEndVNode = newCh[--newEndIdx]
            } else if (sameVnode(oldEndVNode, newStartVNode)) { //4.结束与开始   
                //真实DOM引用
                patchVnode(oldEndVNode, newStartVNode, newCh, newStartIdx)
                nodeOps.insertBefore(parentElm, oldEndVNode.elm, oldStartVNode.elm)
                oldEndVNode = oldCh[--oldEndIdx]
                newStartVNode = newCh[++newStartIdx]
            } else {
                const idxInOld = oldCh.findIndex((node) => {
                    if (node && sameVnode(node, newStartVNode)) {
                        return true
                    }
                })
                if (idxInOld >= 0) {
                    const vnodeToMove = oldCh[idxInOld]
                    patchVnode(vnodeToMove, newStartVNode, newCh, newStartIdx)
                    nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVNode.elm)
                    oldCh[idxInOld] = undefined
                } else {
                    createElm(newStartVNode, parentElm, oldStartVNode.elm, false, newCh, newStartIdx)
                }
                //新数组移动一位，旧数组是否移动就看如果是首位undefined,++
                //末尾undefined , --在上面的逻辑中
                newStartVNode = newCh[++newStartIdx]
            }
        }

        if (oldStartIdx > oldEndIdx) { //如果旧数组少了，那么走此路线         
            let refElm = newCh[newEndIdx + 1]

            //剩余的新数组内容全部插入进去
            for (; newStartIdx <= newEndIdx; ++newStartIdx) {
                let vnode = newCh[newStartIdx]
                createElm(vnode, parentElm, refElm.elm, false, newCh, newStartIdx)
            }
        } else if (newStartIdx > newEndIdx) { //如果新数组少了，那么走此路线
            for (let i = oldStartIdx; i <= oldEndIdx; i++) {
                let vnode = oldCh[i]
                vnode && nodeOps.removeChild(parentElm, vnode.elm)
            }
        }

    }

    return function patch(oldVnode, vnode) {


        if (isUndef(oldVnode)) {
            createElm(vnode)
        } else {
            let isRealElement = isDef(oldVnode.nodeType)
            if (!isRealElement && sameVnode(oldVnode, vnode)) {
                patchVnode(oldVnode, vnode)
            } else {
                if (isRealElement) {
                    oldVnode = emptyNodeAt(oldVnode);
                }
                const parentElm = oldVnode.elm.parentNode
                createElm(vnode, parentElm, oldVnode.elm.nextSibling)
                if (isDef(parentElm) && isDef(oldVnode)) {
                    nodeOps.removeChild(parentElm, oldVnode.elm)
                }
            }
        }

        return vnode.elm
    }
}